package com.minelx.sequence.core;

import java.util.Spliterators;
import java.util.function.IntPredicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.util.Arrays.copyOfRange;

public class StringScanner {

	private final char[] source;

	private int index;

	private StringScanner(char[] source) {
		this.source = source;
	}

	public char nextChar() {
		throwIfEnd();
		return source[index++];
	}

	public boolean hasNext() {
		return index != source.length;
	}

	public String nextAlpha() {
		return collect(Character::isAlphabetic);
	}

	public String nextDigit() {
		return collect(Character::isDigit);
	}

	public String nextPage() {
		return collect(value -> Character.isAlphabetic(value) || Character.isDigit(value));
	}

	public boolean pass(String expected) {
		boolean result = exists(expected);
		if (result) {
			nextChars(expected.length());
		}
		return result;
	}

	public boolean isEmpty() {
		return index == source.length;
	}

	public int countRemains() {
		return source.length - index;
	}

	public boolean exists(String expected) {
		if (isEmpty() || expected.length() > countRemains()) {
			return false;
		}

		char[] chars = copyOfRange(source, index, index + expected.length());
		return new String(chars).equals(expected);
	}

	public String nextChars(int count) {
		return new String(copyOfRange(source, index, index += count));
	}

	public void trim() {
		collect(which -> which == ' ');
	}

	public String nextFloat() {
		return collect(which -> Character.isDigit(which) || which == '.' || which == '-');
	}

	public int getIndex() {
		return index;
	}

	public StringScanner copy() {
		StringScanner result = new StringScanner(source);
		result.index = index;
		return result;
	}

	public boolean existsIf(IntPredicate isTrue) {
		if (isEmpty()) {
			return false;
		}
		return isTrue.test(source[index]);
	}

	public String goes(String content) {
		if (!exists(content)) {
			throw new IllegalStateException("there is no chars like: " + content);
		}
		// pass the chars
		return nextChars(content.length());
	}

	public String til(char endChar) {
		String result = collect(value -> value != endChar);
		if (hasNext()) {
			nextChar(); // endChar
		}
		return result;
	}

	public String collect(IntPredicate neededCollect) {
		StringBuilder result = new StringBuilder();
		while (hasNext()) {
			char next = nextChar();
			if (neededCollect.test(next)) {
				result.append(next);
			} else { // exit
				index -= 1;
				break;
			}
		}
		return result.toString();
	}

	public <R> Stream<R> stream(ReturnMatcher<R, ? super StringScanner> matcher) {
		return StreamSupport.stream(
				Spliterators.spliteratorUnknownSize(matcher.iterator(this), 0),
				false
		);
	}

	private void throwIfEnd() {
		if (!hasNext()) {
			throw new IllegalStateException("scanner source was ended.");
		}
	}

	@Override
	public String toString() {
		return new String(source).substring(index);
	}

	public static StringScanner source(String source) {
		return new StringScanner(source.toCharArray());
	}
}
